﻿namespace SpikingAnalyticsLib.ResponseFingerPrint

open System.IO

//=================================================================================================
/// A matched pair of frame profiles for generating a normalised score
type MatchedProfilePair(nullPatternProfile:FrameProfile, testPatternProfile:FrameProfile) =

//---------------------------------------------------------------------------------------------------------
// Properties

    member this.NullPatternProfile
        with get() =
            nullPatternProfile

    member this.TestPatternProfile
        with get() =
            testPatternProfile

    /// Retrieve a list of the two profiles
    member this.Profiles =
        seq {
            yield nullPatternProfile
            yield testPatternProfile
        }
        |> Seq.toList

//---------------------------------------------------------------------------------------------------------
// Methods

    member this.GetNormalisedScores(frames:ResponseFingerPrintFrameSet, windowMapSet:WindowMapSet, patternFrequencyMap) =
        let nullScores = frames.GetScores(nullPatternProfile.PatternName, windowMapSet, patternFrequencyMap)
        let testScores = frames.GetScores(testPatternProfile.PatternName, windowMapSet, patternFrequencyMap)

        Seq.zip nullScores.Scores testScores.Scores
        |> Seq.map (fun (nullScores, testScores) ->
                let patternScore =
                    let patternAbsentScore = System.Math.Pow(10.0, nullScores.Score)
                    let patternPresentScore = System.Math.Pow(10.0, testScores.Score)    // convert from log value
                    let totalScore = patternAbsentScore + patternPresentScore

                    let normalisedScore = patternPresentScore / totalScore
                    if System.Double.IsNaN(normalisedScore) then
                        0.0
                    else
                        normalisedScore

                let coincidentWindowSpikes =
                    testScores.WindowSpikeActivity
                    |> Seq.filter (fun (window, spiked) -> spiked)
                    |> Seq.length

                // return the normalised probability for each pattern i.e. probability that pattern is present
                patternScore, coincidentWindowSpikes
            )
        |> Seq.toList

//---------------------------------------------------------------------------------------------------------
// Static Methods

    /// Create a new MatchedProfilePair for the specified pattern using selected profiles in the specified folder
    static member Create(folder, basename, networkNumber, patternName) =

        let filenameBuilder = sprintf "%s%d_%s.txt" basename networkNumber
        let profiles = FrameProfile.LoadMany(folder, filenameBuilder, [ "Null"; patternName ], true)

        new MatchedProfilePair(profiles.[0], profiles.[1])

//=================================================================================================

/// A matched set of frame profiles for generating a normalised score
type MatchedProfileSet(profiles:FrameProfile list) =

//---------------------------------------------------------------------------------------------------------
// Properties

    // Get the individual frame profiles that make up this profile set
    member this.FrameProfiles
        with get() =
            profiles

//---------------------------------------------------------------------------------------------------------
// Methods

    /// Get the normalised scores for all profiles and all frames
    /// Generates a set of scores for each pattern name where each score value is normalised
    /// against the corresponding frame score for the other patterns
    member this.GetNormalisedScores(frames:ResponseFingerPrintFrameSet, windowMapSet:WindowMapSet, patternFrequencyMap) =

        // frame scores indexed by pattern name and then frame
        let allScores =
            profiles
            |> Seq.map ( fun profile ->
                    let patternName = profile.PatternName
                    let frameScores = frames.GetScores(patternName, windowMapSet, patternFrequencyMap)
                    patternName, frameScores.Scores
                )
            |> Map.ofSeq

        // need to normalise scores for each frame across patterns
        // therefore invert the data structure so that it is indexed by frame and then pattern name
        // i.e. for each frame ...
        let scoresByFramePattern =
            Seq.init frames.Length (fun index ->

                    // scores for the current frame and for each pattern
                    let patternScores =
                        allScores
                        |> Map.map (fun patternName frameScoreArray ->
                                let frameScore = frameScoreArray.[index]
                                let score = System.Math.Pow(10.0, frameScore.Score) // convert from log value
                                frameScore, score
                            )

                    let normalisedScores =

                        // score total
                        let totalScore =
                            patternScores
                            |> Map.fold (fun state patternName (_, score) -> state + score) 0.0

                        // normalisation using the total
                        patternScores
                        |> Map.map (fun patternName (frameScore, score) ->
                                let normalisedScore =
                                    let temp = score / totalScore
                                    if System.Double.IsNaN(temp) then
                                        0.0
                                    else
                                        temp

                                new FrameScore(frameScore.ResponseFrame, frameScore.PatternName, normalisedScore, frameScore.WindowSpikeActivity)
                            )

                    normalisedScores
                )
            |> Seq.toArray

        // reinvert the data structure so that it is indexed by pattern name and then frame
        let scoresByPatternFrame =

            scoresByFramePattern
            |> Seq.mapi (fun index patternScores ->
                    patternScores
                    |> Map.toSeq
                    |> Seq.map (fun (patternName, frameScore) -> patternName, index, frameScore)
                )
            |> Seq.concat
            |> Seq.fold (fun (state:Map<string, _>) (patternName, index, frameScore) ->
                    let scores =
                        if state.ContainsKey patternName then
                            state.[patternName]
                        else
                            Map.empty
                    Map.add patternName (Map.add index frameScore scores) state
                ) Map.empty
            |> Map.map (fun patternName scoreMap ->
                    let scores = Array.init scoreMap.Count (fun index -> scoreMap.[index])
                    new FrameScoreSet(scores)
                )

        scoresByPatternFrame

//---------------------------------------------------------------------------------------------------------
// Static Methods

    /// Create a new MatchedProfileSet using selected profiles in the specified folder
    static member Create(folder, filenameBuilder, patternNames) =

        let profiles =
            FrameProfile.LoadMany(folder, filenameBuilder, patternNames, true)
            |> Seq.toList

        new MatchedProfileSet(profiles)

//=================================================================================================
